/*
 * Copyright 2009 Lukasz Wozniak
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 *
 * http://www.apache.org/licenses/LICENSE-2.0 
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */
package pl.lwozniak.blip;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import pl.lwozniak.blip.entities.Bliposphere;
import pl.lwozniak.blip.entities.Error;
import pl.lwozniak.blip.entities.Status;
import pl.lwozniak.blip.entities.Update;
import pl.lwozniak.blip.message.BlipMessagePart;
import pl.lwozniak.blip.message.FileMessagePart;
import pl.lwozniak.blip.message.TextMessagePart;
import pl.lwozniak.http.AbstractHttpConnectorFactory;
import pl.lwozniak.http.HttpConnector;
import pl.lwozniak.http.HttpConnectorFactory;
import pl.lwozniak.http.HttpHeader;
import pl.lwozniak.http.HttpProxyConfig;
import pl.lwozniak.http.HttpRequestException;
import pl.lwozniak.http.HttpResult;

/**
 * Blip connector allows making requests and analyzing responses to and from
 * blip.pl
 * 
 * @author Lukasz Wozniak
 * 
 */
public class BlipConnector {
	private static final int METHOD_GET = 1;
	private static final int METHOD_POST = 2;
	private static final int METHOD_PUT = 3;
	private static final int METHOD_DELETE = 4;

	private static final String BLIP_URL = "http://api.blip.pl";
	private String username;
	private String authentication;
	private HttpConnector connector;
	private final String baseUrl;
	static final String PATH_UPDATES = "/updates";
	private static final String USER_AGENT = "yajba 0.1.0-alpha";
	private static final int MAX_LIMIT = 20;

	private final HttpHeader[] DEFAULT_HEADERS = new HttpHeader[] {
			new HttpHeader("User-Agent", USER_AGENT),
			new HttpHeader("X-Blip-API", "0.02"),
			new HttpHeader("Accept-Charset", HttpConnector.DEFAULT_ENCODING),
			new HttpHeader("Accept", "application/json") };

	private HttpHeader[] authHeaders;

	public BlipConnector(HttpProxyConfig proxy, String customUrl)
			throws BlipConnectorException {
		super();
		HttpConnectorFactory f = AbstractHttpConnectorFactory.newInstance();
		f.setHttpProxyConfig(proxy);
		connector = f.getConnector();
		connector.setDefaultHeaders(DEFAULT_HEADERS);
		baseUrl = (customUrl == null ? BLIP_URL : customUrl);
		
	}
	
	public BlipConnector(String username, String password,
			HttpProxyConfig proxy, String customUrl)
			throws BlipConnectorException {
		this(proxy,customUrl);
		this.username = username;
		String auth = username + ":" + password;
		try {
			auth = new String(Encoder.encode(auth.getBytes("UTF-8")), "UTF-8");
		} catch (UnsupportedEncodingException e) {
			// Java w/o UTF-8?
		}
		this.authentication = auth;
		authHeaders = new HttpHeader[] { new HttpHeader("Authorization", "Basic " + authentication)};
	}
	
	

	/**
	 * Executes HTTP requests.
	 * 
	 * @param method
	 * @param path
	 * @param body
	 * @param auth
	 * @return
	 * @throws BlipConnectorException
	 */
	private HttpResult execute(int method, URI path, BlipMessagePart body) throws BlipConnectorException {
		try {
			HttpResult res = null;
			switch (method) {
			case METHOD_GET:

				res = connector.doGet(authHeaders
						, path, body);
				break;
			case METHOD_POST:

				res = connector.doPost(authHeaders
						, path, body);
				break;
			case METHOD_PUT:

				res = connector.doPut(authHeaders
						, path, body);
				break;
			case METHOD_DELETE:

				res = connector.doDelete(authHeaders
						, path, body);
				break;

			default:
				throw new IllegalArgumentException("Unknown HTTP method "
						+ method);
			}
			System.out.println(res);
			return res;
		} catch (HttpRequestException e) {
			throw new BlipConnectorException(e);
		}

	}

	/**
	 * Handles result, throws exceptions according to HTTP code.
	 * 
	 * @param result
	 * @return body of request, may be null.
	 * @throws BlipUnauthorizedException
	 * @throws BlipNotFoundException
	 * @throws BlipConnectorException
	 */
	private String handleResult(HttpResult result)
			throws BlipUnauthorizedException, BlipNotFoundException,
			BlipConnectorException {
		switch (result.getCode()) {
		// good codes
		case 200:
			return result.getBody();
		case 201:
			return result.getBody();
			// bad but expected codes
		case 401:
			throw new BlipUnauthorizedException(result.getCode(), result
					.getMessage());
		case 404:
			throw new BlipNotFoundException(result.getCode(), result
					.getMessage());
			// bad request, try to handle
		case 400:
			throw new BlipBadRequestException(result.getCode(), result
					.getMessage(), extractError(result.getBody()));
			// bad! errors

		case 422:
		case 503:
		default:
			throw new BlipConnectorException(result.getCode(), result
					.getMessage());
		}
	}

	private Error extractError(String body) {
		if (body != null && body.length() > 0 && body.charAt(0) == '{') {
			try {
				return new Error(new JSONObject(body));
			} catch (JSONException e) {
				// if it's not blip error JSON object then ignore
			}
		}
		return null;
	}

	/**
	 * Returns updates from all users.
	 * 
	 * @param limit
	 *            how many updates.
	 * @param since
	 *            id of last update.
	 * @return
	 * @throws BlipConnectorException
	 */
	public List<Update> getAllUpdates(int limit, long since)
			throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_UPDATES);
			if (since > 0) {
				sb.append("/").append(since).append("/all_since");
			} else {
				sb.append("/all");
			}
			if (limit > 0) {
				sb.append("?limit=").append(limit);
			}

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		return getUpdates(uri);
	}

	/**
	 * Returns current users updates.
	 * 
	 * @param limit
	 *            how many updates.
	 * @param since
	 *            id of last update.
	 * @return
	 * @throws BlipConnectorException
	 */
	public List<Update> getUpdates(int limit, long since)
			throws BlipConnectorException {
		return getUserUpdates(limit, since, username);
	}

	/**
	 * Returns updates for specified user.
	 * 
	 * @param limit
	 *            how many updates.
	 * @param since
	 *            id of last update.
	 * @param user
	 *            username whose updates we want to get.
	 * @return
	 * @throws BlipConnectorException
	 */
	public List<Update> getUserUpdates(int limit, long since, String user)
			throws BlipConnectorException {
		if (limit > MAX_LIMIT) {
			limit = MAX_LIMIT;
		}

		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					"/users/").append(user).append(PATH_UPDATES);
			if (since > 0) {
				sb.append("/").append(since).append("/since");
			}
			if (limit > 0) {
				sb.append("?limit=").append(limit);
			}

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		return getUpdates(uri);
	}

	/**
	 * Returns updates for uri.
	 * 
	 * @param uri
	 * @return
	 * @throws BlipConnectorException
	 */
	private List<Update> getUpdates(URI uri) throws BlipConnectorException {
		HttpResult out = execute(METHOD_GET, uri, null);
		String res = handleResult(out);
		if (res != null) {
			try {
				JSONArray array = new JSONArray(res);
				ArrayList<Update> ret = new ArrayList<Update>(array.length());
				for (int i = 0; i < array.length(); i++) {
					ret.add(new Update(array.getJSONObject(i)));
				}
				return ret;
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Returns update for specified id.
	 * 
	 * @param id
	 * @return
	 * @throws BlipConnectorException
	 *             for general error
	 */
	public Update getUpdate(long id) throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(new StringBuilder().append(baseUrl).append(
					PATH_UPDATES).append("/").append(id).toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Update(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Deletes update with given id.
	 * 
	 * @param id
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipConnectorException
	 */
	public void deleteUpdate(long id) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_UPDATES).append("/").append(id);
			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		HttpResult res = execute(METHOD_DELETE, uri, null);
		handleResult(res);
	}

	/**
	 * Creates new status with given body.
	 * 
	 * @param content
	 * @return created status
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipConnectorException
	 */
	public Status createUpdate(String content) throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(baseUrl + "/updates");
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		TextMessagePart part = new TextMessagePart("update[body]", content);
		HttpResult out = execute(METHOD_POST, uri, part);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Status(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			throw new BlipConnectorException("Unexpected error");
		}

	}

	/**
	 * Creates new status with given body.
	 * 
	 * @param content
	 * @param attach
	 * @param filename
	 * @return
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipConnectorException
	 */
	public Status createUpdate(String content, InputStream attach,
			String filename) throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(baseUrl + "/updates");
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		BlipMessagePart part = new TextMessagePart("user_id", username);
		part.addNext(new FileMessagePart("image/jpeg", "update[picture]",
				filename, attach));
		part.addNext(new TextMessagePart("update[body]", content));
		part.addNext(new TextMessagePart("recipent_id", "0"));
		HttpResult out = execute(METHOD_POST, uri, part);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Status(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			throw new BlipConnectorException("Unexpected error");
		}

	}

	/**
	 * @param content
	 * @param target
	 * @return
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipBadRequestException
	 *             when target user does not exist
	 * @throws BlipConnectorException
	 *             for general errors
	 */
	public Status createMessageUpdate(String content, String target)
			throws BlipConnectorException {
		return createUpdate(">" + target + ":" + content);
	}

	/**
	 * Returns current Bliposphere content
	 * 
	 * @return
	 * @throws BlipConnectorException
	 */
	public Bliposphere getBliposphere() throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(baseUrl+"/bliposphere");
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		HttpResult out = execute(METHOD_GET, uri, null);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Bliposphere(new JSONArray(res));
			} catch (JSONException e) {
				throw new BlipConnectorException("Unable to parse reply", e);
			}
		} else {
			return null;
		}
	}

}
